﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using BenevolentSunData;

namespace BenevolentSun
{

    public class Level
    {

        #region Map / Selector Data
        Map map;
        public Map Map { get { return map; } }
        
        
        Selector selector;
        public Cell SelectedCell
        {
            get { return map.GetCell(selector.Coords); }
            set { selector.Coords = value.Coords; }
        }

        float cellOffset = 39f;
        #endregion


        #region Movement / Interation / View
        public Boolean Interact()
        {
            if (SelectedCell.Interact())
            {
                map.InitBeams();
                if (SelectedCell is MirrorCell)
                {
                    AudioManager.PlayCue("Thud");
                }
                if (SelectedCell is PrismCell)
                {
                    AudioManager.PlayCue("Pop");
                }
                return true;
            }

            return false;
        }

        public void MoveSelector(Point delta)
        {
            selector.Coords.X = Math.Min(Math.Max(0, selector.Coords.X + delta.X), Map.MapDimensions.X - 1);
            selector.Coords.Y = Math.Min(Math.Max(0, selector.Coords.Y + delta.Y), Map.MapDimensions.Y - 1);
        }

        public void MoveViewConstrained(Vector2 delta)
        {
            camera.OrientationMove(delta);

            camera.At = new Vector3(
                Math.Min(Math.Max(0, camera.At.X), (map.MapDimensions.X - 1) * 39f),
                camera.At.Y,
                Math.Min(Math.Max(0, camera.At.Z), (map.MapDimensions.Y - 1) * 39f));
            camera.Eye = new Vector3(
                camera.At.X,
                camera.Eye.Y,
                camera.At.Z + 250f);

            //camera.Eye.X = camera.At.X = Math.Min(Math.Max(0, camera.At.X), (map.MapDimensions.X - 1) * 39f);
            //camera.At.Z = Math.Min(Math.Max(0, camera.At.Z), (map.MapDimensions.Y - 1) * 39f);
            //camera.Eye.Z = camera.At.Z + 250f;
        }
        
        public void SelectCellAt(Vector3 position)
        {
            selector.Coords.X = Math.Min(Math.Max(0, (int)(position.X / cellOffset)), Map.MapDimensions.X - 1);
            selector.Coords.Y = Math.Min(Math.Max(0, (int)(position.Z / cellOffset)), Map.MapDimensions.Y - 1);
        }

        public void ViewCell(Cell cell)
        {
            Vector3 cellPos = new Vector3(cell.Coords.X * cellOffset, 0f, cell.Coords.Y * cellOffset);
            camera.Eye = new Vector3(0f, 500f, 250f) + cellPos;
            camera.At = cellPos;
        }

        public void ViewSelector()
        {
            Vector3 cellPos = new Vector3(selector.Coords.X * cellOffset, 0f, selector.Coords.Y * cellOffset);
            camera.Eye = new Vector3(0f, 500f, 250f) + cellPos;
            camera.At = cellPos;
        }
        #endregion


        #region Graphics
        public GraphicsDevice GraphicsDevice;

        Camera camera;
        public Camera Camera { get { return camera; } }

        VertexDeclaration beamVertexDeclaration;
        BasicEffect beamEffect;
        Texture2D beamTexture;
        #endregion


        #region Content
        public ContentManager Content;
        #endregion


        #region Initialization
        public Level(Map map, ContentManager content, GraphicsDevice graphics)
        {
            this.map = map;
            GraphicsDevice = graphics;
            Content = content;

            camera = new Camera(GraphicsDevice, new Vector3(0f, 500f, 250f), Vector3.Zero, 1f, 1500f, MathHelper.ToRadians(45f));

            beamTexture = Content.Load<Texture2D>("Textures/Beam");

            selector = new Selector(Content.Load<Model>("Models/Selector"), map.Skin.GetMaterial("Selector"), new Point(0, 0));
            SelectedCell = map.Cells[0, 0];

            map.InitBeams();

            if (map.SourceCells.Count != 0)
            {
                SelectedCell = map.SourceCells[0];
                ViewCell(SelectedCell);
            }
            else
            {
                SelectedCell = map.Cells[0, 0];
                ViewCell(SelectedCell);
            }

            // Init Beam Graphics data and effect
            beamVertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionColorTexture.VertexElements);
            beamEffect = new BasicEffect(GraphicsDevice, null);
            beamEffect.VertexColorEnabled = true;
            beamEffect.TextureEnabled = true;
            beamEffect.Texture = beamTexture;
            beamEffect.FogEnabled = map.Fog.Enabled;
            beamEffect.FogColor = map.Fog.Color.ToVector3();
            beamEffect.FogStart = map.Fog.Start;
            beamEffect.FogEnd = map.Fog.End;
            
            BuildDrawLists();
        }
        #endregion


        #region Updating
        public void Update(GameTime gameTime)
        {
            camera.Update();

            selector.Update(gameTime);
        }
        #endregion


        #region Drawing
        Cell[] floorDrawList;

        Cell[] cellDrawList;

        private void BuildDrawLists()
        {
            List<Cell> floorDrawListTemp = new List<Cell>();
            List<Cell> cellDrawListTemp = new List<Cell>();

            foreach (Cell cell in map.Cells)
            {
                if (cell is NullCell)
                    continue;
                if (cell is EmptyCell == false)
                    cellDrawListTemp.Add(cell);
                if (cell is WallCell || cell is SourceCell)
                    continue;
                floorDrawListTemp.Add(cell);
            }

            cellDrawList = cellDrawListTemp.ToArray();
            floorDrawList = floorDrawListTemp.ToArray();
        }

        private void SetSharedShaderProperties()
        {
            map.SwitchEffect.Parameters["View"].SetValue(camera.View);
            map.SwitchEffect.Parameters["EyePosition"].SetValue(camera.Eye);
            map.SwitchEffect.Parameters["Projection"].SetValue(camera.Proj);

            if (map.ShaderModel3)
            {
                Effect effect = map.SwitchEffect;
                List<PointLight> pointLights = map.PointLights;
                List<BeamLight> beamLights = map.BeamLights;
                int pointLightsCount = Math.Min(8, pointLights.Count);
                effect.Parameters["PointLightsCount"].SetValue(pointLightsCount);

                for (int l = 0; l < pointLightsCount; ++l)
                {

                    PointLight pointLight = pointLights[l];
                    pointLight.UpdateLight(l);
                }

                if (!Settings.DisableBeamLighting)
                {
                    int beamLightsCount = Math.Min(32, beamLights.Count);
                    effect.Parameters["BeamLightsCount"].SetValue(beamLightsCount);

                    for (int l = 0; l < beamLightsCount; ++l)
                    {

                        BeamLight beamLight = beamLights[l];
                        beamLight.UpdateLight(l);
                    }
                }
                else
                {
                    effect.Parameters["BeamLightsCount"].SetValue(0);
                }
            }
        }

        public void Draw(GameTime gameTime)
        {
            // Set the render state
            GraphicsDevice.RenderState.AlphaBlendEnable = true;
            GraphicsDevice.RenderState.DepthBufferEnable = true;
            GraphicsDevice.RenderState.AlphaTestEnable = true;
            GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            // Set shared shader properties
            SetSharedShaderProperties();

            // Draw Floors
            foreach (Cell cell in floorDrawList)
            {
                cell.DrawFloor(new Vector3(cell.Coords.X * cellOffset, 0f, cell.Coords.Y * cellOffset));
            }

            // Draw Selector
            Vector3 selectionPosition = new Vector3((selector.Coords.X * cellOffset), 30.0f, (selector.Coords.Y * cellOffset) + (cellOffset / 2) + 5f);
            selector.DrawEx(selectionPosition, camera);

            // Draw Cells
            foreach (Cell cell in cellDrawList)
            {
                cell.Draw(new Vector3(cell.Coords.X * cellOffset, 0f, cell.Coords.Y * cellOffset));
            }

            // Draw Beams
            DrawBeams();
        }

        private void DrawBeams()
        {
            // Set render state
            GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
            if (!map.ShaderModel3)
            {
                // Ensure blending is set properly
                GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            }

            // Set shader properties
            beamEffect.Projection = camera.Proj;
            beamEffect.View = camera.View;

            // Initialize the graphics pipeline and effect
            GraphicsDevice.VertexDeclaration = beamVertexDeclaration;
            beamEffect.Begin();

            // Render each beam segment
            //Matrix orientation = Matrix.Identity;
            Vector3 axis = new Vector3();
            float length;
            bool changed = camera.Changed;
            Vector3 eye = camera.Eye;
            foreach (Map.BeamSegment segment in Map.Segments)
            {
                // Orient the beam in the world to match the segment by setting its world matrix if need be
                if (changed || !segment.Initialized)
                {
                    segment.Initialized = true;
                    Vector3.Subtract(ref segment.End, ref segment.Start, out axis);
                    length = axis.Length();
                    axis /= length;
                    Matrix.CreateConstrainedBillboard(ref segment.Start, ref eye, ref axis, camera.LookAt, camera.Up, out segment.World);
                    segment.World = Matrix.CreateScale(1f, 1f, length) * Matrix.CreateWorld(segment.Start, -segment.World.Up, segment.World.Forward);
                }
                beamEffect.World = segment.World;

                // Send the verts to the pipeline
                beamEffect.CurrentTechnique.Passes[0].Begin();
                GraphicsDevice.DrawUserPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleFan, segment.VertexBuffer, 0, 2);
                beamEffect.CurrentTechnique.Passes[0].End();
            }
            
            // End the effect
            beamEffect.End();

            // Restore render state
            GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
        }
        #endregion

    }

}
